﻿using System;
using System.Linq;
using System.Reflection;
using BruteMerge.ExecutableLauncher.Attributes;
using BruteMerge.ExecutableLauncher.Interfaces;

namespace BruteMerge.ExecutableLauncher
{
    internal class Program
    {
        private static IEntryPointDescription GetEntryPointDescription()
        {
            return typeof (Program).Assembly.GetCustomAttributes(false)
                .OfType<EntryPointAttribute>()
                .Single();
        }

        private static ResourceProvider CreateResourceProvider()
        {
            var assembly = typeof (Program).Assembly;
            var aliases = assembly.GetCustomAttributes(false).OfType<ResourceAliasAttribute>();

            return new ResourceProvider(assembly, aliases);
        }

        private static IApplicationFlags GetApplicationFlags()
        {
            return typeof (Program).Assembly.GetCustomAttributes(false)
                .OfType<ApplicationFlagsAttribute>()
                .Single();
        }

        private const string ENTRY_POINT_METHOD = "Main";

        private static MethodInfo GetEntryPoint(ApplicationAssemblyLoader assemblyLoader, IEntryPointDescription entryPointDescription )
        {
            var entryAssembly = assemblyLoader.LoadAssembly(entryPointDescription.Assembly);
            var entryClass = entryAssembly.GetType(entryPointDescription.Class);

            if (entryClass == null)
            {
                Console.WriteLine("Error: entry class {0} not found in {1}",
                                  entryPointDescription.Class, entryPointDescription.Assembly);
            
                Environment.Exit(1);
            }

            return entryClass.GetMethod(ENTRY_POINT_METHOD, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static);
        }


        private static int Main(string[] args)
        {
            var resourceProvider = CreateResourceProvider();
            var entryPointDescription = GetEntryPointDescription();

            var flags = GetApplicationFlags();
            Debug.Enabled = flags.EnableDebugPrintInLoader;

            Debug.Println("=========================================");
            Debug.Println("Starting loader ...");

            Debug.Println("Entry point is class {1} in assembly {0}",
                          entryPointDescription.Assembly, entryPointDescription.Class);

            var assemblyLoader = new ApplicationAssemblyLoader(resourceProvider, AppDomain.CurrentDomain);
            var entryPoint = GetEntryPoint(assemblyLoader, entryPointDescription);

            Debug.Println("Starting application ...");
            Debug.Println("=========================================");

            try
            {
                var rc = entryPoint.Invoke(null, new object[] {args});

                // TOTAL INTEGER COVERAGE
                if (rc is long) return (int) (long) rc;
                if (rc is int) return (int) rc;
                if (rc is short) return (short) rc;
                if (rc is sbyte) return (sbyte) rc;

                if (rc is ulong) return (int) (ulong) rc;
                if (rc is uint) return (int) (uint) rc;
                if (rc is ushort) return (ushort) rc;
                if (rc is byte) return (byte) rc;

                // if not
                return 0;
            }
            catch (TargetInvocationException exception)
            {
                throw exception.InnerException;
            }
        }
    }
}
